
#include <unordered_map>
#include <functional>
#include <stdlib.h>
#include <vm.h>
#include <iostream>


const char *reg_name[] = {
    "zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1",
    "t2",   "t3", "t4", "t5", "t6", "t7", "s0", "s1", "s2", "s3",
    "s4",   "s5", "s6", "s7", "t8", "t9", "k0", "k1", "gp", "sp",
    "fp",   "ra"
};


std::unordered_map<std::string, const int> reg_index = {
  {"zero", 0},
};


#define rd      i.rd()
#define rs      i.rs()
#define rt      i.rt()

using exec_t = void(*)(VM*, Instruction);
using dump_t = std::string(*)(Instruction);

struct InstructionInfo {
  exec_t exec;
  const char *name;
  dump_t dump;
};

extern InstructionInfo r_isa[1<<6];
extern InstructionInfo r2_isa[1<<6];
extern InstructionInfo i_isa[1<<6];


// add word 
// 000000 100000
void add(VM* vm, Instruction i) {
    vm->s[rd] = vm->s[rs] + vm->s[rt];
}

// add immediate word
// 001000
void addi(VM *vm, Instruction i) {
    vm->s[rt] = vm->s[rs] + i.simm();
}

// addimmediate unsigned word
// 001001
void addiu(VM* vm, Instruction i) {
    vm->u[rt] = vm->u[rs] + i.simm();
}

// add unsigned word
// 000000 100001
void addu(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rs] + vm->u[rt];
}
std::string dump_addu(Instruction i) {
  return "";
}

// and
// 000000 100100
void vm_and(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rs] & vm->u[rt];
}

// and immediate
// 001100
void andi(VM *vm, Instruction i) {
    vm->u[rt] = vm->u[rs] & i.uimm();
}

// branch
// allways: beq zero, zero, offset
void b(VM *vm, Instruction i) { vm->pc += i.simm() << 2; }

// branch on equal
// 000100
void beq(VM *vm, Instruction i) {
    if (vm->u[rs] == vm->u[rt]) {
        vm->pc += i.simm()<<2;
    }
}

// branch if not equal
// 000101
void bne(VM *vm, Instruction i) {
    if (vm->u[rs] != vm->u[rt]) {
        vm->pc += i.simm()<<2;
    }
}
// div word
// 000000 011010
void vm_div(VM *vm, Instruction i) {
    vm->hi = vm->s[rs] / vm->s[rt];
    vm->lo = vm->s[rs] % vm->s[rt];
}

// div unsigned word
//  00000 011011
void divu(VM *vm, Instruction i) {
    vm->hi = vm->u[rs] / vm->u[rt];
    vm->lo = vm->u[rs] % vm->u[rt];
}

// jump
// 000010
void j(VM *vm, Instruction i) {
    vm->pc = (vm->pc & 0xF0000000) | i.J()<<2;
}

// jump and link
// 000011
void jal(VM *vm, Instruction i) {
    vm->ra = vm->pc;
    vm->pc = (vm->pc & 0xF00000000) | i.J()<<2;
}

// jump and link register
void jalr(VM *vm, Instruction i) {
    vm->s[rd] = vm->pc;
    vm->pc = vm->s[rs];
}
// jump register
void jr(VM* vm, Instruction i) {
    vm->pc = vm->s[rs];
}

// load byte
void lb(VM *vm, Instruction i) {
    vm->s[rt] = *(int8_t *)vm->addr(vm->s[rs]+i.simm());
}
// load byte unsigned
void lbu(VM *vm, Instruction i) {
    vm->u[rt] = *(uint8_t *)vm->addr(vm->s[rs]+i.simm());
}

void li(VM *vm, Instruction i) {}

// load halfword
void lh(VM *vm, Instruction i) {
    vm->s[rt] = *(int16_t *)vm->addr(vm->s[rs]+i.simm());
}
// load halfword unsigned
void lhu(VM *vm, Instruction i) {
    vm->u[rt] = *(uint16_t *)vm->addr(vm->s[rs]+i.simm());
}

// load upper immediate
void lui(VM *vm, Instruction i) {
    vm->u[rt] = i.uimm() << 16;
}

// load word
void lw(VM *vm, Instruction i) {
    vm->u[rt] = *(uint32_t *)vm->addr(vm->s[rs]+i.simm());
}

std::string dump_lw(Instruction i) {
    return Sprintf("%s\t%s,%d(%s)", r_isa[i.op()].name, reg_name[rt], i.simm(), reg_name[rs]);
}

// load word left
void lwl(VM *vm, Instruction i) {
    // TODO
}

// load word right
void lwr(VM *vm, Instruction i) {
    // TODO
}

// multiply and add word to hi, lo
void madd(VM *vm, Instruction i) {
    int64_t a = vm->s[rs], b = vm->s[rt];
    int64_t c = ((int64_t)vm->hi << 32) + vm->lo;
    c += a * b;
    vm->hi = c>>32;
    vm->lo = c&0xFFFFFFFF;
}

// move from hi
void mfhi(VM *vm, Instruction i) {
    vm->u[rd] = vm->hi;
}
// move from lo
void mflo(VM *vm, Instruction i) {
    vm->u[rd] = vm->lo;
}

// move to hi
void mthi(VM *vm, Instruction i) {
  vm->hi = vm->u[rs];
}
// move to lo
void mtlo(VM *vm, Instruction i) {
  vm->lo = vm->u[rs];
}

std::string dump_mfhilo(Instruction i) {
  return Sprintf("%s\t%s", r_isa[i.func()].name, reg_name[rd]);
}
std::string dump_mthilo(Instruction i) {
  return Sprintf("%s\t%s", r_isa[i.func()].name, reg_name[rs]);
}

// move on not zero
void movn(VM *vm, Instruction i) {
    if (vm->u[rt] != 0) {
        vm->u[rd] = vm->u[rs];
    }
}

void sub(VM* vm, Instruction i) {
    vm->s[rd] = vm->s[rs] - vm->s[rt];
}
void subu(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rs] - vm->u[rt];
}

void vm_or(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rs] | vm->u[rt];
}
std::string dump_or(Instruction i) {
  if (rs == 0 || rt == 0) return Sprintf("%s\t%s,%s", "move", reg_name[rd], reg_name[rs+rt]);
  return Sprintf("%s\t%s,%s,%s", "or", reg_name[rd], reg_name[rs], reg_name[rt]);
}

void vm_xor(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rs] ^ vm->u[rt];
}
void vm_nor(VM* vm, Instruction i) {
    vm->u[rd] = ~(vm->u[rs] | vm->u[rt]);
}
void slt(VM* vm, Instruction i) {
    if (vm->s[rs] < vm->s[rt]) vm->s[rd] = 1;
    else vm->s[rd] = 0;
}
void sltu(VM* vm, Instruction i) {
    if (vm->u[rs] < vm->u[rt]) vm->u[rd] = 1;
    else vm->u[rd] = 0;
}
void sll(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rt] << i.shamt();
}
void srl(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rt] >> i.shamt();
}
void sra(VM* vm, Instruction i) {
    vm->s[rd] = vm->s[rt] >> i.shamt();
}
void sllv(VM* vm, Instruction i) {
    vm->s[rd] = vm->s[rt] << vm->s[rs];
}
void srlv(VM* vm, Instruction i) {
    vm->u[rd] = vm->u[rt] >> vm->u[rs];
}
void srav(VM* vm, Instruction i) {
    vm->s[rd] = vm->s[rt] >> vm->u[rs];
}


void nop(VM* vm, Instruction i) {}

enum : uint32_t {
    kNone = 0,
    kMalloc,
    kFree,
    kOpen,
    kCreat,
    kClose,
    kWrite,
    kRead,
    kLseek,
};

void syscall(VM* vm, Instruction i) {
  switch (vm->a0) {
    case kMalloc:
    case kFree:
    case kOpen:
    case kCreat:
    case kClose:
    case kWrite:
    case kRead:
    case kLseek:
    default: break;
  }
}

// or immediate
void ori(VM *vm, Instruction i) {
    vm->u[rt] = vm->u[rs] | i.uimm();
}

// xor immediate
void xori(VM *vm, Instruction i) {
    vm->u[rt] = vm->u[rs] ^ i.uimm();
}

// store byte
void sb(VM *vm, Instruction i) {
    uint8_t *addr = (uint8_t *)vm->addr(vm->s[rs]+i.simm());
    *addr = (uint8_t)vm->u[rt];
}

// store halfword
void sh(VM *vm, Instruction i) {
    uint16_t *addr = (uint16_t *)vm->addr(vm->s[rs]+i.simm());
    *addr = (uint16_t)vm->u[rt];
}

// store word
void sw(VM *vm, Instruction i) {
    uint32_t *addr = (uint32_t *)vm->addr(vm->s[rs]+i.simm());
    *addr = vm->u[rt];
}
std::string dump_lwsw(Instruction i) {
  return Sprintf("%s\t%s,%d(%s)", i_isa[i.op()].name, reg_name[rt], i.simm(), reg_name[rs]);
}

// mult
void mult(VM *vm, Instruction i) {
}
// mult unsigned
void multu(VM *vm, Instruction i) {
}

void slti(VM *vm, Instruction i) {
    if ((int)vm->s[rs] < i.simm()) vm->s[rd] = 1;
    else vm->s[rd] = 0;
}
void sltiu(VM *vm, Instruction i) {
    if (vm->s[rs] < i.simm()) vm->s[rd] = 1;
    else vm->s[rd] = 0;
}

void mul(VM *vm, Instruction i) {
    vm->u[rd] = vm->u[rs] * vm->u[rt];
}

#define INST(_I, _name) [_I] = {_name, #_name, dump_##_name}

InstructionInfo r_isa[1<<6] = {
  [000] = {sll, "sll"},
  [001] = {},
  [002] = {srl, "srl"},
  [003] = {sra, "sra"},
  [004] = {sllv, "sllv"},
  [005] = {},
  [006] = {srlv, "srlv"},
  [007] = {srav, "srav"},

  [010] = {jr, "jr"},
  [011] = {},
  [012] = {},
  [013] = {},
  [014] = {syscall, "syscall"},
  [015] = {},
  [016] = {},
  [017] = {},

  [020] = {mfhi, "mfhi", dump_mfhilo},
  [021] = {mthi, "mthi", dump_mthilo},
  [022] = {mflo, "mflo", dump_mfhilo},
  [023] = {mtlo, "mtlo", dump_mthilo},
  [024] = {},
  [025] = {},
  [026] = {},
  [027] = {},

  [030] = {mult, "mult"},
  [031] = {multu, "multu"},
  [032] = {vm_div, "div"},
  [033] = {divu, "divu"},
  [034] = {},
  [035] = {},
  [036] = {},
  [037] = {},

  [040] = {add, "add"},
  [041] = {addu, "addu"},
  [042] = {sub, "sub"},
  [043] = {subu, "subu"},
  [044] = {vm_and, "and"},
  [045] = {vm_or, "or", dump_or},
  [046] = {vm_xor, "xor"},
  [047] = {vm_nor, "nor"},

  [050] = {},
  [051] = {},
  [052] = {slt, "slt"},
  [053] = {sltu, "sltu"},
  [054] = {},
  [055] = {},
  [056] = {},
  [057] = {},

  [060] = {},
  [061] = {},
  [062] = {},
  [063] = {},
  [064] = {},
  [065] = {},
  [066] = {},
  [067] = {},

  [070] = {},
  [071] = {},
  [072] = {},
  [073] = {},
  [074] = {},
  [075] = {},
  [076] = {},
  [077] = {},
};

InstructionInfo r2_isa[1<<6] = {
  [000] = {},
  [001] = {},
  [002] = {mul, "mul"},
  [003] = {},
  [004] = {},
  [005] = {},
  [006] = {srlv, "srlv"},
  [007] = {srav, "srav"},

  [010] = {jr, "jr"},
  [011] = {},
  [012] = {},
  [013] = {},
  [014] = {syscall, "syscall"},
  [015] = {},
  [016] = {},
  [017] = {},

  [020] = {mfhi, "mfhi", dump_mfhilo},
  [021] = {mthi, "mthi", dump_mthilo},
  [022] = {mflo, "mflo", dump_mfhilo},
  [023] = {mtlo, "mtlo", dump_mthilo},
  [024] = {},
  [025] = {},
  [026] = {},
  [027] = {},

  [030] = {mult, "mult"},
  [031] = {multu, "multu"},
  [032] = {vm_div, "div"},
  [033] = {divu, "divu"},
  [034] = {},
  [035] = {},
  [036] = {},
  [037] = {},

  [040] = {add, "add"},
  [041] = {addu, "addu"},
  [042] = {sub, "sub"},
  [043] = {subu, "subu"},
  [044] = {vm_and, "and"},
  [045] = {vm_or, "or", dump_or},
  [046] = {vm_xor, "xor"},
  [047] = {vm_nor, "nor"},

  [050] = {},
  [051] = {},
  [052] = {slt, "slt"},
  [053] = {sltu, "sltu"},
  [054] = {},
  [055] = {},
  [056] = {},
  [057] = {},

  [060] = {},
  [061] = {},
  [062] = {},
  [063] = {},
  [064] = {},
  [065] = {},
  [066] = {},
  [067] = {},

  [070] = {},
  [071] = {},
  [072] = {},
  [073] = {},
  [074] = {},
  [075] = {},
  [076] = {},
  [077] = {},
};

void r_exec(VM *vm, Instruction i) { r_isa[i.func()].exec(vm, i); }
void r2_exec(VM *vm, Instruction i) {r2_isa[i.func()].exec(vm, i);}

InstructionInfo i_isa[1<<6] = {
  [000] = {r_exec, ""},
  [001] = {},
  [002] = {j, "j"},
  [003] = {jal, "jal"},
  [004] = {beq, "beq"},
  [005] = {bne, "bne"},
  [006] = {},
  [007] = {},

  [010] = {addi, "addi"},
  [011] = {addiu, "addiu"},
  [012] = {slti, "slti"},
  [013] = {sltiu, "sltiu"},
  [014] = {andi, "andi"},
  [015] = {ori, "ori"},
  [016] = {xori, "xori"},
  [017] = {lui, "lui"},

  [020] = {},
  [021] = {},
  [022] = {},
  [023] = {},
  [024] = {},
  [025] = {},
  [026] = {},
  [027] = {},

  [030] = {},
  [031] = {},
  [032] = {},
  [033] = {},
  [034] = {r2_exec},
  [035] = {},
  [037] = {},

  [040] = {},
  [041] = {},
  [042] = {},
  [043] = {lw, "lw", dump_lwsw},
  [044] = {},
  [045] = {},
  [046] = {},
  [047] = {},

  [050] = {},
  [051] = {},
  [052] = {},
  [053] = {sw, "sw", dump_lwsw},
  [054] = {},
  [055] = {},
  [056] = {},
  [057] = {},

  [060] = {},
  [061] = {},
  [062] = {},
  [063] = {},
  [064] = {},
  [065] = {},
  [066] = {},
  [067] = {},

  [070] = {},
  [071] = {},
  [072] = {},
  [073] = {},
  [074] = {},
  [075] = {},
  [076] = {},
  [077] = {},
};


VM::VM(void *base, size_t size) : 
    _base(static_cast<char *>(base)), _size(size) {
    memset(reg, 0, sizeof reg);
    pc = hi = lo = 0;
    sp = (size & ~15UL);
}

void VM::run() {
    for (;;) {
        Instruction i = fetch();
        if (i.exit()) break;
        i_isa[i.op()].exec(this, i);
        zero = 0; // zero always 0
    }
}


std::string dump(Instruction i) {
  if (i.op() == 0) {
    if (r_isa[i.func()].dump) return r_isa[i.func()].dump(i);
    if (i.shamt() != 0) return Sprintf("%s\t%s,%s,%d", r_isa[i.func()].name, reg_name[rd], reg_name[rt], i.shamt());
    if (rd == 0)  {
      if (rt == 0)
        return Sprintf("%s\t%s", r_isa[i.func()].name, reg_name[rs]);
      return Sprintf("%s\t%s,%s", r_isa[i.func()].name, reg_name[rs], reg_name[rt]);
    }
    return Sprintf("%s\t%s,%s,%s", r_isa[i.func()].name, reg_name[rd], reg_name[rs], reg_name[rt]);
  } else if (i.op() == 002 || i.op() == 003) {
    return Sprintf("%s\t0x%x", i_isa[i.op()].name, i.J());
  }
  if (i_isa[i.op()].dump) return i_isa[i.op()].dump(i);
  return Sprintf("%s\t%s,%s,%d", i_isa[i.op()].name, reg_name[rt], reg_name[rs], i.simm());
}


std::vector<std::string> VM::dump() {
    std::vector<std::string> res;
    for (;;) {
        Instruction i = fetch();
        if (i.exit()) break;
        res.push_back(::dump(i));
    }
    pc = 0;
    return res;
}

VM::~VM() {}

